/*
 * 描          述:  <描述>
 * 修  改   人:  Administrator
 * 修改时间:  2020年3月2日
 * <修改描述:>
 */
package com.tx.component.file.resource.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.StringUtils;

import com.tx.component.file.exception.FileContextResourceException;
import com.tx.component.file.resource.AbstractFCResource;
import com.tx.component.file.resource.FCResource;
import com.tx.component.file.resource.FCResourceLoader;
import com.tx.component.file.resource.FCResourceTypeEnum;
import com.tx.component.file.util.FileContextUtils;
import com.tx.core.exceptions.resource.ResourceAccessException;
import com.tx.core.exceptions.util.AssertUtils;
import com.tx.core.util.MessageUtils;

/**
 * 基于spring的Resource实现的资源加载器<br/>
 * <功能详细描述>
 * 
 * @author  Administrator
 * @version  [版本号, 2020年3月2日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
public class LocalFileFCResourceLoader
        implements FCResourceLoader, InitializingBean {
    
    /** 日志记录句柄 */
    protected static Logger logger = LoggerFactory
            .getLogger(FCResourceLoader.class);
    
    /** 文件夹目录 */
    private String catalog;
    
    /** 目录文件资源 */
    private FileSystemResource catalogResource;
    
    /** <默认构造函数> */
    public LocalFileFCResourceLoader() {
        super();
    }
    
    /** <默认构造函数> */
    public LocalFileFCResourceLoader(String catalog, String path) {
        super();
        this.catalog = catalog;
        this.catalogResource = new FileSystemResource(path);
    }
    
    /** <默认构造函数> */
    public LocalFileFCResourceLoader(String catalog, File folder) {
        super();
        this.catalog = catalog;
        this.catalogResource = new FileSystemResource(folder);
    }
    
    /**
     * @throws IOException 
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() {
        AssertUtils.notEmpty(this.catalog, "catalog is empty.");
        
        //初始化资源目录
        if (!this.catalogResource.exists()) {
            try {
                FileUtils.forceMkdir(this.catalogResource.getFile());
            } catch (IOException e) {
                throw new ResourceAccessException("初始化文件资源目录异常，文件夹创建失败.");
            }
        }
        if (!this.catalogResource.getFile().isDirectory()) {
            throw new ResourceAccessException("初始化文件资源目录异常，文件或已存在并且不为目录.");
        }
    }
    
    /**
     * @param relativePath
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath) {
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new LocalFileFCResource(this.catalog,
                this.catalogResource, relativePath);
        return fileResource;
    }
    
    /**
     * @param relativePath
     * @param type
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath,
            FCResourceTypeEnum type) {
        AssertUtils.notNull(type, "type is null.");
        
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new LocalFileFCResource(this.catalog,
                this.catalogResource, relativePath, type);
        return fileResource;
    }
    
    /**
     * @return 返回 catalog
     */
    public String getCatalog() {
        return catalog;
    }
    
    /**
     * @param 对catalog进行赋值
     */
    public void setCatalog(String catalog) {
        this.catalog = catalog;
    }
    
    /**
     * SFSRFileResource实现
     * Spring_FileSystemResource_FileResource
     * <功能详细描述>
     * 
     * @author  Administrator
     * @version  [版本号, 2020年3月2日]
     * @see  [相关类/方法]
     * @since  [产品/模块版本]
     */
    public static class LocalFileFCResource extends AbstractFCResource {
        
        /** 文件资源 */
        private FileSystemResource catalogResource;
        
        /** 文件资源 */
        private FileSystemResource resource;
        
        /** <默认构造函数> */
        public LocalFileFCResource(String catalog,
                FileSystemResource catalogResource, String relativePath,
                FCResourceTypeEnum type) {
            this(catalog, catalogResource, relativePath);
            
            if (this.type == null) {
                this.type = type;
            }
        }
        
        /** <默认构造函数> */
        public LocalFileFCResource(String catalog,
                FileSystemResource catalogResource, String relativePath) {
            super(catalog);
            AssertUtils.notEmpty(catalog, "catalog is empty.");
            AssertUtils.notNull(catalogResource, "catalogResource is null.");
            AssertUtils.notNull(relativePath, "relativePath is null.");
            this.catalogResource = catalogResource;
            
            //处理相对路径
            relativePath = StringUtils.cleanPath(relativePath);
            if (!relativePath.startsWith("/")) {
                relativePath = "/" + relativePath;
            }
            
            //获取对应的文件
            try {
                this.resource = new FileSystemResource(
                        StringUtils.applyRelativePath(
                                this.catalogResource.getFile()
                                        .getCanonicalPath() + "/",
                                relativePath));
            } catch (Exception e) {
                throw new ResourceAccessException("创建LocalFileFCResource异常.",
                        e);
            }
            
            //初始化文件类型
            doGetType();
            
            //初始化相对路径
            initRelativePath();
        }
        
        /** <默认构造函数> */
        public LocalFileFCResource(String catalog,
                FileSystemResource catalogResource, FileSystemResource resource,
                FCResourceTypeEnum type) {
            this(catalog, catalogResource, resource);
            
            if (this.type == null) {
                this.type = type;
            }
        }
        
        /** <默认构造函数> */
        public LocalFileFCResource(String catalog,
                FileSystemResource catalogResource,
                FileSystemResource resource) {
            super(catalog);
            AssertUtils.notEmpty(catalog, "catalog is empty.");
            AssertUtils.notNull(catalogResource, "catalogResource is null.");
            AssertUtils.notNull(resource, "resource is null.");
            this.catalogResource = catalogResource;
            
            this.resource = resource;
            
            //初始化文件类型
            doGetType();
            
            //初始化相对路径
            initRelativePath();
        }
        
        /**
         * 初始化相对路径
         * <功能详细描述> [参数说明]
         * 
         * @return void [返回类型说明]
         * @exception throws [异常类型] [异常说明]
         * @see [类、类#方法、类#成员]
         */
        private void initRelativePath() {
            try {
                String catalogCanonicalPath = this.catalogResource.getFile()
                        .getCanonicalPath();
                String resourceCanonicalPath = this.resource.getFile()
                        .getCanonicalPath();
                if (!resourceCanonicalPath.startsWith(catalogCanonicalPath)) {
                    throw new ResourceAccessException(MessageUtils.format(
                            "资源路径，应在所在目录规范路径的下级.resourceCanonicalPath:{},catalogCanonicalPath:{}",
                            new Object[] { resourceCanonicalPath,
                                    catalogCanonicalPath }));
                }
                
                //将相对路径写入
                setRelativePath(StringUtils.cleanPath(resourceCanonicalPath
                        .substring(catalogCanonicalPath.length())));
            } catch (IOException e) {
                throw new ResourceAccessException("读取资源文件规范路径信息失败.", e);
            }
        }
        
        /**
         * @return 返回 catalogResource
         */
        public FileSystemResource getCatalogResource() {
            return catalogResource;
        }
        
        /**
         * @return 返回 resource
         */
        public FileSystemResource getResource() {
            return resource;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        protected FCResourceTypeEnum doGetType() {
            if (this.resource.exists()) {
                if (this.resource.getFile().isDirectory()) {
                    return FCResourceTypeEnum.FOLDER;
                } else if (this.resource.getFile().isFile()) {
                    return FCResourceTypeEnum.FILE;
                }
            }
            return null;
        }
        
        /**
         * @param relativePath
         * @return
         * @throws IOException
         */
        @Override
        public FCResource createRelative(String relativePath) {
            //拼接相对路径
            String pathToUse = StringUtils.applyRelativePath(getRelativePath(),
                    relativePath);
            
            //组装实际资源对象
            FCResource resource = new LocalFileFCResource(this.getCatalog(),
                    this.catalogResource, pathToUse);
            return resource;
        }
        
        /**
         * @param name
         * @return
         * @throws IOException
         */
        @Override
        public FCResource getChild(String name) {
            //拼接相对路径
            String pathToUse = StringUtils
                    .applyRelativePath(getRelativePath() + "/", name);
            
            //组装实际资源对象
            FCResource resource = new LocalFileFCResource(this.getCatalog(),
                    this.catalogResource, pathToUse);
            return resource;
        }
        
        /**
         * @return
         */
        @Override
        public boolean exists() {
            return this.resource.exists();
        }
        
        /**
         * @return
         */
        @Override
        public long getSize() {
            return this.resource.getFile().length();
        }
        
        /**
         * 删除文件或删除空文件夹
         * 当文件夹不为空时会抛出异常
         * @throws IOException
         */
        @Override
        public void delete() throws FileContextResourceException {
            if (this.resource.exists()) {
                try {
                    FileUtils.delete(this.resource.getFile());
                } catch (IOException e) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "FCResource.delete() error. catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()), e);
                }
            }
        }
        
        /**
         * @return
         */
        @Override
        public void deleteQuietly() throws FileContextResourceException {
            FileUtils.deleteQuietly(this.resource.getFile());
        }
        
        /**
         * @throws IOException
         */
        @Override
        public void createFile() throws FileContextResourceException {
            File newFile = this.resource.getFile();
            if (!newFile.exists()) {
                try {
                    FileUtils.forceMkdirParent(newFile);
                    newFile.createNewFile();
                } catch (IOException e) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "FCResource.createFile() error. catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()), e);
                }
                
            } else {
                if (!isFile()) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "创建文件失败,文件存在，但不为文件.catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()));
                }
            }
        }
        
        /**
         * @throws IOException
         */
        @Override
        public void createFolder() throws FileContextResourceException {
            if (!this.resource.exists()) {
                try {
                    FileUtils.forceMkdir(this.resource.getFile());
                } catch (IOException e) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "FCResource.createFolder() error. catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()), e);
                }
            } else {
                if (!isFolder()) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "创建文件目录失败,文件存在，但不为文件夹.catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()));
                }
            }
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public List<FCResource> getChildren()
                throws FileContextResourceException {
            List<FCResource> resList = new ArrayList<>();
            if (!exists()) {
                return resList;
            }
            if (!isFolder()) {
                return resList;
            }
            for (String filename : this.resource.getFile().list()) {
                resList.add(this.createRelative(
                        this.resource.getFilename() + "/" + filename));
            }
            return resList;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream()
                throws FileContextResourceException {
            OutputStream outputStream = null;
            try {
                outputStream = FileUtils
                        .openOutputStream(this.resource.getFile(), false);
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getOutputStream() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return outputStream;
        }
        
        /**
         * @param bAppend
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream(boolean bAppend)
                throws FileContextResourceException {
            OutputStream outputStream = null;
            try {
                outputStream = FileUtils
                        .openOutputStream(this.resource.getFile(), bAppend);
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getOutputStream(boolean) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return outputStream;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public InputStream getInputStream()
                throws FileContextResourceException {
            InputStream ins = null;
            try {
                ins = this.resource.getInputStream();
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getInputStream() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return ins;
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void save(InputStream inputStream)
                throws FileContextResourceException {
            AssertUtils.notNull(inputStream, "inputStream is null.");
            
            try {
                //如果文件不存在，则自动进行创建
                if (!this.resource.exists()) {
                    //如果不存在，则检查文件所在的目录是否存在
                    File file = this.resource.getFile();
                    File folder = file.getParentFile();
                    if (!folder.exists()) {
                        //如果目录不存在，则创建对应目录
                        folder.mkdirs();
                    }
                    file.createNewFile();
                }
                try (OutputStream output = resource.getOutputStream()) {
                    IOUtils.copy(inputStream, output);
                }
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.save(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void add(InputStream inputStream)
                throws FileContextResourceException {
            AssertUtils.notNull(inputStream, "inputStream is null.");
            
            try {
                //如果文件不存在，则自动进行创建
                if (this.resource.exists()) {
                    throw new FileContextResourceException(
                            "新增资源错误，对应资源已经存在.如需覆盖请调用save方法.");
                }
                //获取文件对象并对其进行创建
                File file = this.resource.getFile();
                File folder = file.getParentFile();
                if (!folder.exists()) {
                    //如果目录不存在，则创建对应目录
                    folder.mkdirs();
                }
                file.createNewFile();
                //写入文件
                try (OutputStream output = resource.getOutputStream()) {
                    IOUtils.copy(inputStream, output);
                }
            } catch (IOException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.add(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        ///**
        // * @param inputStream
        // * @throws FileContextResourceException
        // */
        //@Override
        //public void append(InputStream inputStream)
        //        throws FileContextResourceException {
        //    AssertUtils.notNull(inputStream, "inputStream is null.");
        //    
        //    try {
        //        //获取文件对象并对其进行创建
        //        File file = this.resource.getFile();
        //        File folder = file.getParentFile();
        //        if (!folder.exists()) {
        //            //如果目录不存在，则创建对应目录
        //            folder.mkdirs();
        //        }
        //        if (!file.exists()) {
        //            file.createNewFile();
        //        }
        //        //写入文件
        //        try (OutputStream output = this.getOutputStream(true)) {
        //            IOUtils.copy(inputStream, output);
        //        }
        //    } catch (IOException e) {
        //        throw new FileContextResourceException(MessageUtils.format(
        //                "FCResource.append(InputStream) error. catalog:{},path:{}.",
        //                getCatalog(),
        //                getRelativePath()), e);
        //    }
        //}
        
    }
}
